﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using ProLinq.Projection;
using ProLinq.Projection.Configuration;

namespace ProLinq.Tests.Projection
{
	[TestClass]
	public class BaseTests
	{
		#region Test Data

		private IQueryable<A> GetSource()
		{
			return new[]
				{
					new A {DataString1 = "str11", DataString2 = "str21", DataInt1 = 11, DataInt2 = 21, DataInt3 = 31, Relations = new A[1] { new A(){ DataInt1 = 1} }},
					new A1 {DataString1 = "str12", DataString2 = "str22", DataInt1 = 12, DataInt2 = 22, DataInt3 = 32, DataInt4 = 5},
					new A {DataString1 = "str13", DataString2 = "str23", DataInt1 = 13, DataInt2 = 23, DataInt3 = 33},
				}.AsQueryable();
		}

		private IQueryable<A> GetSourceWithDerived()
		{
			return new[]
				{
					new A {DataString1 = "str11", DataString2 = "str21", DataInt1 = 11, DataInt2 = 21, DataInt3 = 31},
					new A {DataString1 = "str12", DataString2 = "str22", DataInt1 = 12, DataInt2 = 22, DataInt3 = 32},
					new A {DataString1 = "str13", DataString2 = "str23", DataInt1 = 13, DataInt2 = 23, DataInt3 = 33},
				}.AsQueryable();
		}

		#endregion

		[TestMethod]
		public void ReturnsNotNull()
		{
			IQueryable<A> source = GetSource();
			IQueryable<B> projection = source.Project().To<B>();

			Assert.IsNotNull(projection);
		}

		[TestMethod]
		public void ReturnsSameCount()
		{
			IQueryable<A> source = GetSource();
			IQueryable<B> projection = source.Project().To<B>();

			Assert.AreEqual(projection.Count(), source.Count());
		}

		[TestMethod]
		public void DefaultConfiguration_AllSameNameAndTypeMembersAreMapped()
		{
			IQueryable<A> source = GetSource();
			IQueryable<B> projection = source.Project().To<B>();

			A[] sourceArray = source.ToArray();
			B[] projectionArray = projection.ToArray();

			for (Int32 i = 0; i < sourceArray.Length; i++)
			{
				A s = sourceArray[i];
				B p = projectionArray[i];
				Assert.AreEqual(s.DataString1, p.DataString1);
				Assert.AreEqual(s.DataInt2, p.DataInt2);
			}
		}

		[TestMethod]
		public void FactoryConfiguration_AllSpecifiedMembersAreMapped()
		{
			IQueryable<A> source = GetSource();
			IQueryable<B> projection = source.Project().To(x => new B
				{
					DataInt1 = x.DataInt1.ToString()
				});

			A a = source.First();
			B b = projection.First();

			Assert.AreEqual(a.DataInt1.ToString(), b.DataInt1);
		}

		[TestMethod]
		public void FactoryConfiguration_DerivedSourceTypes()
		{
			IQueryable<A> source = GetSourceWithDerived();
			IQueryable<B> projection = source.Project().To(x => new B
			{
				DataInt1 = x.DataInt1.ToString()
			});

			var res = projection.ToArray();
		}

		[TestMethod]
		public void FactoryConfiguration_OnlySpecifiedMembersAreMapped()
		{
			IQueryable<A> source = GetSource();
			IQueryable<B> projection = source.Project().To(x => new B
				{
					DataInt1 = x.DataInt1.ToString()
				});

			A a = source.First();
			B b = projection.First();

			Assert.AreEqual(b.DataInt2, default(Int32));
			Assert.AreEqual(b.DataInt3, default(Int32));
			Assert.AreEqual(b.DataString1, default(String));
			Assert.AreEqual(b.DataString3, default(String));
		}

		[TestMethod]
		public void CrossChainProjectionIsWorking()
		{
			IQueryable<A> source = GetSource();
			IQueryable<Temp1> query = source.Project().To<B>()
			                                .Select(b => new Temp1 {DataInt2 = b.DataInt2, B = b})
			                                .Select(t => new Temp2 {DataInt2 = t.DataInt2, B = t.B})
			                                .Select(t => new Temp1 {DataInt3 = t.DataInt2, B = t.B});
			List<Temp1> res = query.ToList();
		}


		[TestMethod]
		public void SourceProviderCreateQueryIsCalled()
		{
			var projConfiguration = new ProjectionConfiguration<A, B>();
			projConfiguration.MainProjection.Factory = a => new B();

			var sourceQueryableProvider = new Mock<IQueryProvider>();

			var sourceQueryable = new Mock<IQueryable<A>>();
			sourceQueryable.SetupGet(x => x.Provider).Returns(sourceQueryableProvider.Object);


			var projectionProvider = new ProjectQueryProvider(sourceQueryable.Object, projConfiguration);

			ConstantExpression expr = Expression.Constant(new B[0]);
			projectionProvider.Execute(expr);

			sourceQueryableProvider.Verify(x => x.CreateQuery<B>(expr));
		}


		[TestMethod]
		public void AnonymousProjectionIsWorking()
		{
			var configuaration = ProjectionConfigurationFactory.Instance.CreateConfiguration<A, B>();
			configuaration.EnablePartialProjection = false;
			IQueryable<A> source = GetSource();
			var query = source.Project().To<B>(configuaration)
											.Select(b => new { DataInt2 = b.DataInt2, B = b })
											.Select(t => new { DataInt2 = t.DataInt2, B = t.B })
											.Select(t => new { DataInt3 = t.DataInt2, B = t.B });
			var res = query.ToList();
		}

		[TestMethod]
		public void NullTypeProjectionIsWorking()
		{
			IQueryable<A> source = GetSource();
			var query = source.Project().To<B>(c =>
				{
					c.EnablePartialProjection = false;
					c.ForceAnonymousProjection = true;
				}).Select(b => b.DataString1.Length > 0 ? new { Val = b.DataInt2 } : null);

			var res = query.ToArray();
		}

		[TestMethod]
		public void EnumerableProjectionIsWorking()
		{
			IQueryable<A> source = GetSource();
			var query = source.Project(c => { 
				c.EnablePartialProjection = false;
				c.ForceAnonymousProjection = true;
			}).Cast<A>().Select(a => new
								  {
									  A = a,
									  RelData = a.Relations.Select(r => new
									  {
										  R = r,
										  V = r.DataInt1
									  })
								  });
			var res = query.AsEnumerable().Cast<Object>().ToArray();
		}
	}
}